#include "stdint.h"
#include "text_video.h"
#include "oapl.h"
#include "optlib.h"
#include "string.h"

#define GOLDEN_RATIO        0x9E3779B9

#define TEA_ROUNDS          32
#define TEA_SUM_MAGIC       ((GOLDEN_RATIO * TEA_ROUNDS) & 0xFFFFFFFF)

#define XTEA_ROUNDS         64
#define XTEA_SUM_MAGIC      ((GOLDEN_RATIO * XTEA_ROUNDS) & 0xFFFFFFFF)

#define XXTEA_CIPHER    (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ ((z << 4)) ^ (sum ^ y)) + (key[(i & 0x03) ^ e] ^ z))

static const char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


// TEA { 128-bit key, 64-bit blocks }
static void TEA_encrypt_block(uint32_t block[2], const uint32_t const key[4]) 
{
    size_t i;
    uint32_t sum = 0x00000000;
    
    for (i = 0; i < TEA_ROUNDS; i++) 
    {
        sum += GOLDEN_RATIO;
        block[0] += ((block[1] << 4) + key[0]) ^ (block[1] + sum) ^ ((block[1] >> 5) + key[1]);
        block[1] += ((block[0] << 4) + key[2]) ^ (block[0] + sum) ^ ((block[0] >> 5) + key[3]);  
    }
}

// encrypt a block stream with TEA (requires a block count that's divisible by 2)
void TEA_encrypt(uint32_t * block, const uint32_t * const key, const size_t count)
{
    size_t i;
    
    for(i = 0; i < count; i += 2)
    { TEA_encrypt_block(block + i, key); }
}
 
static void TEA_decrypt_block(uint32_t block[2], const uint32_t const key[4]) 
{
    size_t i;
    uint32_t sum = TEA_SUM_MAGIC;
    
    for(i = 0; i < TEA_ROUNDS; i++)
    {
        block[1] -= ((block[0] << 4) + key[2]) ^ (block[0] + sum) ^ ((block[0] >> 5) + key[3]);
        block[0] -= ((block[1] << 4) + key[0]) ^ (block[1] + sum) ^ ((block[1] >> 5) + key[1]);
        sum -= GOLDEN_RATIO;
    }
}

void TEA_decrypt(uint32_t * block, const uint32_t * const key, const size_t count)
{
    size_t i;
    
    for(i = 0; i < count; i += 2)
    { TEA_decrypt_block(block + i, key); }
}

// XTEA { 128-bit key, 64-bit blocks }
static void XTEA_encrypt_block(uint32_t block[2], const uint32_t const key[4]) 
{
    size_t i;
    uint32_t sum = 0x00000000;
    
    for(i = 0; i < XTEA_ROUNDS; i++) 
    {
        block[0] += (((block[1] << 4) ^ (block[1] >> 5)) + block[1]) ^ (sum + key[sum & 0x03]);
        sum += GOLDEN_RATIO;
        block[1] += (((block[0] << 4) ^ (block[0] >> 5)) + block[0]) ^ (sum + key[(sum >> 11) & 0x03]);
    }
}
 
// encrypt a block stream with TEA (requires a block count that's divisible by 2)
void XTEA_encrypt(uint32_t * block, const uint32_t * const key, const size_t count)
{
    size_t i;
    
    for(i = 0; i < count; i += 2)
    { XTEA_encrypt_block(block + i, key); }
}
 
static void XTEA_decrypt_block(uint32_t block[2], const uint32_t const key[4]) 
{
    size_t i;
    uint32_t sum = XTEA_SUM_MAGIC;
    
    for(i = 0; i < XTEA_ROUNDS; i++) 
    {
        block[1] -= (((block[0] << 4) ^ (block[0] >> 5)) + block[0]) ^ (sum + key[(sum >> 11) & 0x03]);
        sum -= GOLDEN_RATIO;
        block[0] -= (((block[1] << 4) ^ (block[1] >> 5)) + block[1]) ^ (sum + key[sum & 0x03]);
    }
}

void XTEA_decrypt(uint32_t * block, const uint32_t * const key, const size_t count)
{
    size_t i;
    
    for(i = 0; i < count; i += 2)
    { XTEA_decrypt_block(block + i, key); }
}

// XXTEA { 128-bit key, arbitrary block size (specified in "n") }
void XXTEA_encrypt_block(uint32_t * block, const uint32_t const key[4], uint32_t n)
{
    uint32_t z, y;
    uint32_t i, q, e;
    uint32_t sum = 0x00000000;

    if(n < 2)
    { return; }

    z = block[n - 1];
    q = 6 + (52 / n);
    y = block[0];
    
    while(q-- > 0)
    {
        sum += GOLDEN_RATIO;
        e = (sum >> 2) & 0x03;
        
        for(i = 0; i < (n - 1); i++)
        {
            y = block[i + 1];
            block[i] += XXTEA_CIPHER;
            z = block[i];
        }
        
        y = block[0];
        block[n - 1] += XXTEA_CIPHER;
        z = block[n - 1];
    }
}

void XXTEA_decrypt_block(uint32_t * block, const uint32_t const key[4], uint32_t n)
{
    uint32_t z, y;
    uint32_t i, q, e;
    uint32_t sum;

    if(n < 2)
    { return; }

    z = block[n - 1];
    q = 6 + (52 / n);
    y = block[0];
    sum = q * GOLDEN_RATIO;

    while(sum != 0)
    {
        e = (sum >> 2) & 0x03;
        
        for(i = n - 1; i > 0; i--)
        {
            z = block[i - 1];
            block[i] -= XXTEA_CIPHER;
            y = block[i];
        }
        
        z = block[n - 1];
        block[0] -= XXTEA_CIPHER;
        y = block[0];
        sum -= GOLDEN_RATIO;
    }
}

unsigned char RC4s[256];
unsigned int RC4i, RC4j;
 
void RC4_init(unsigned char * key, unsigned int key_length) 
{
    for (RC4i = 0; RC4i < 256; RC4i++)
    { RC4s[RC4i] = RC4i; }
 
    for (RC4i = RC4j = 0; RC4i < 256; RC4i++) 
    {
        unsigned char temp;
 
        RC4j = (RC4j + key[RC4i % key_length] + RC4s[RC4i]) & 0xFF;
        temp = RC4s[RC4i];
        RC4s[RC4i] = RC4s[RC4j];
        RC4s[RC4j] = temp;
    }
 
    RC4i = RC4j = 0;
}

unsigned char RC4_output() 
{
    unsigned char temp;
 
    RC4i = (RC4i + 1) & 255;
    RC4j = (RC4j + RC4s[RC4i]) & 255;
 
    temp = RC4s[RC4j];
    RC4s[RC4j] = RC4s[RC4i];
    RC4s[RC4i] = temp;
 
    return RC4s[(temp + RC4s[RC4j]) & 255];
}



// BASE64 { 24-bit blocks }
void base64_encode(const uint8_t block[3], char out[4], const size_t len)
{
    out[0] = cb64[block[0] >> 2];
    out[1] = cb64[((block[0] & 0x03) << 4) | ((block[1] & 0xF0) >> 4)];
    out[2] = (uint8_t)(len > 1 ? cb64[((block[1] & 0x0F) << 2) | ((block[2] & 0xC0) >> 6)] : '=');
    out[3] = (uint8_t)(len > 2 ? cb64[block[2] & 0x3F] : '=');
}

void base64_decode(const char in[4], uint8_t out[3])
{   
    out[0] = (uint8_t)(in[0] << 2 | in[1] >> 4);
    out[1] = (uint8_t)(in[1] << 4 | in[2] >> 2);
    out[2] = (uint8_t)(((in[2] << 6) & 0xC0) | in[3]);
}

void base64_string_decode(const char * in, uint8_t * out)
{
    size_t i, x, len;
    
    i = x = 0;
    len = strlen(in);
    
    while(len > 0)
    {
        base64_decode(in + i, out + x);
        
        i += 4;
        x += 3;
        len -= 4; 
    }
}

void base64_string_encode(const uint8_t * block, char * out, size_t len)
{
    size_t i, x;
    
    i = x = 0;
    
    while(len > 0)
    {
        if(len >= 3)
        {
            base64_encode(block + i, out + x, 3);
        }
        else
        {
            base64_encode(block + i, out + x, len);
            return;
        }
        
        i += 3;
        x += 4;
        len -= 3; 
    }
}








